/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package message

import (
	"encoding/xml"
	"io/ioutil"
	"net/http"
	"strings"
)

/**
接收微信服务器事件推送
在微信用户和公众号产生交互的过程中，用户的某些操作会使得微信服务器通过事件推送
的形式通知到开发者在开发者中心处设置的服务器地址，从而开发者可以获取到该信息。
其中，某些事件推送在发生后，是允许开发者回复用户的，某些则不允许。

微信服务器以 xml 格式 POST 数据到公众号服务器。

参数说明：
 参数	          | 描述
==========================================
 ToUserName	      | 开发者微信号
------------------------------------------
 FromUserName	  | 发送方帐号（一个OpenID）
------------------------------------------
 CreateTime	      | 消息创建时间 （整型）
------------------------------------------
 MsgType	      | 消息类型，event
------------------------------------------
 Event	          | 事件类型
------------------------------------------
 EventKey	      | 事件KEY值
------------------------------------------
 Ticket	          | 二维码的ticket
------------------------------------------
 Latitude	      | 地理位置纬度
------------------------------------------
 Longitude	      | 地理位置经度
------------------------------------------
 Precision	      | 地理位置精度
------------------------------------------

事件类型说明：

名称                                   | Event           |  额外
======================================================================================================
关注                                   | subscribe       |
------------------------------------------------------------------------------------------------------
取消关注                               | unsubscribe     |
------------------------------------------------------------------------------------------------------
未关注用户扫描带参数二维码              | subscribe       |  EventKey：qrscene_为前缀，后面为二维码的参数值
                                                            Ticket：  二维码的ticket，可用来换取二维码图片
------------------------------------------------------------------------------------------------------
已关注用户扫描带参数二维码              | SCAN            |  EventKey: 是一个32位无符号整数，即创建二维码时
                                                                      的二维码scene_id
                                                            Ticket:   二维码的ticket，可用来换取二维码图片
------------------------------------------------------------------------------------------------------
上报地理位置                           | LOCATION        |   Latitude:  地理位置纬度
                                                            Longitude: 地理位置经度
                                                            Precision: 地理位置精度
------------------------------------------------------------------------------------------------------
点击菜单拉取消息                       | CLICK           |   EventKey: 与自定义菜单接口中KEY值对应
------------------------------------------------------------------------------------------------------
点击菜单跳转链接                       | VIEW            |   EventKey: 设置的跳转URL
------------------------------------------------------------------------------------------------------
*/

type UserMsgEvent string

const (
	Subscribe   UserMsgEvent = "SUBSCRIBE"
	Unsubscribe UserMsgEvent = "UNSUBSCRIBE"
	Scan        UserMsgEvent = "SCAN"
	Location    UserMsgEvent = "LOCATION"
	Click       UserMsgEvent = "CLICK"
	View        UserMsgEvent = "VIEW"
	TemplateMsg UserMsgEvent = "TEMPLATESENDJOBFINISH" // 模板消息发送之后微信服务器会推送一个事件消息
)

// 微信服务器发出来的消息
type UserMsgType string

const (
	// 用户点击菜单栏
	UserMsgTypeEvent UserMsgType = "event"
	// 用户发送文本消息
	UserMsgTypeText = "text"
	// 用户发送图片
	UserMsgTypeImage = "image"
	// 用户发送语音
	UserMsgTypeVoice = "voice"
	// 用户发送视频
	UserMsgTypeVideo = "video"
	// 用户发送短视频
	UserMsgTypeShortVideo = "shortvideo"
	// 用户发送位置信息
	UserMsgTypeLocation = "location"
	// 用户发送连接
	UserMsgTypeLink = "link"
)

// 用户发送给服务器的消息
type UserMessage struct {
	ToUserName   string       `desc:"微信原始ID"`
	FromUserName string       `desc:"发送方帐号（一个OpenID）"`
	CreateTime   int64        `desc:"消息创建时间（整型）"`
	MsgType      UserMsgType  `desc:"消息类型，event/text/image/voice/video/shortvideo/location/link"`
	Content      string       `desc:"文本消息内容"`
	MsgID        int64        `desc:"消息id，64位整型"`
	PicUrl       string       `desc:"图片链接（由系统生成）"`
	MediaId      int          `desc:"图片消息媒体id，可以调用多媒体文件下载接口拉取数据。"`
	Format       string       `desc:"语音格式，如amr，speex等"`
	Recognition  string       `desc:"语音识别结果，UTF8编码"`
	ThumbMediaId string       `desc:"视频消息缩略图的媒体id，可以调用多媒体文件下载接口拉取数据。"`
	Location_X   string       `desc:"地理位置维度"`
	Location_Y   string       `desc:"地理位置经度"`
	Scale        string       `desc:"地图缩放大小"`
	Label        string       `desc:"地理位置信息"`
	Title        string       `desc:"消息标题"`
	Description  string       `desc:"消息描述"`
	Url          string       `desc:"消息链接"`
	Event        UserMsgEvent `desc:"事件类型"`
	EventKey     string       `desc:"事件KEY值"`
	Ticket       string       `desc:"二维码的ticket，可用来换取二维码图片"`
	Latitude     int          `desc:"地理位置纬度"`
	Longitude    int          `desc:"地理位置经度"`
	Precision    int          `desc:"地理位置精度"`
	Host         string       `desc:"触发该事件的域名, 该字段数据由本地添加, 必定为安全域名"`
	Status       string       `desc:"暂时只出现在模板消息发送状态通知中, [success]-[发送成功] [failed:user block]-[用户屏蔽消息] [failed: system failed]-[系统错误]"`
}

func (um *UserMessage) HostUrl(Url string) string {
	if Url != "" && !strings.HasPrefix(Url, "/") {
		Url = "/" + Url
	}
	if !strings.HasPrefix(um.Host, "http") {
		um.Host = "http://" + um.Host
	}
	return um.Host + Url
}

var UserMsgDispatcher = NewUserMsgDispatcher()

type UserMsgHandler func(msg *UserMessage, writer http.ResponseWriter)

type userMsgDispatcher struct {
	events         map[UserMsgEvent][]UserMsgHandler
	msgs           map[UserMsgType][]UserMsgHandler
	beforeHandlers []UserMsgHandler
	afterHandlers  []UserMsgHandler
}

// 监听所有消息, 并设置前置处理回调函数
func (d *userMsgDispatcher) ListenBefore(handler UserMsgHandler) {
	d.beforeHandlers = append(d.beforeHandlers, handler)
}

// 监听所有消息, 并设置后置处理回调函数
func (d *userMsgDispatcher) ListenAfter(handler UserMsgHandler) {
	d.afterHandlers = append(d.afterHandlers, handler)
}

// 监听指定类型的消息
func (d *userMsgDispatcher) ListenMsg(umt UserMsgType, handler UserMsgHandler) {
	d.msgs[umt] = append(d.msgs[umt], handler)
}

// 监听指定的事件
func (d *userMsgDispatcher) ListenEvt(event UserMsgEvent, handler UserMsgHandler) {
	d.events[event] = append(d.events[event], handler)
}

// 触发事件
func (d *userMsgDispatcher) Trigger(evt *UserMessage, writer http.ResponseWriter) {
	// 触发 before handler
	for _, handler := range d.beforeHandlers {
		handler(evt, writer)
	}
	// 触发 events
	if evt.MsgType == UserMsgTypeEvent {
		for _, handler := range d.events[evt.Event] {
			handler(evt, writer)
		}
	}

	// 触发 messages
	for _, handler := range d.msgs[evt.MsgType] {
		handler(evt, writer)
	}

	// 触发 after handler
	for _, handler := range d.afterHandlers {
		handler(evt, writer)
	}
}

func NewUserMsgDispatcher() *userMsgDispatcher {
	return &userMsgDispatcher{
		events: make(map[UserMsgEvent][]UserMsgHandler, 20),
		msgs:   make(map[UserMsgType][]UserMsgHandler, 20),
	}
}

// 读取用户发送/触发的消息, 读取之后可以使用 UserMsgDispatcher.Trigger() 触发
func ReadUserMessage(req *http.Request) (umsg *UserMessage, err error) {
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}
	req.Body.Close()
	umsg = &UserMessage{}
	err = xml.Unmarshal(data, umsg)
	if err != nil {
		return nil, err
	} else {
		host := req.Host // host 为安全域名的 host, 由微信服务器发送
		umsg.Host = host
		if umsg.Event != "" {
			umsg.Event = UserMsgEvent(strings.ToUpper(string(umsg.Event)))
		}
		return umsg, nil
	}
}
